package com.sway.data.core;

import com.sway.data.annotation.Column;
import com.sway.data.exception.ResultMapperException;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ResultSetMapper<T> implements BaseMapper<T>{

    private Map<String, Field> columnMap;
    private ResultSet resultSet;

    private final Class<T> resultClass;



    public ResultSetMapper(T entity) {
        this(entity.getClass());
    }
    public ResultSetMapper(Class resultClass) {
        this.resultClass = resultClass;
        map();
    }



    public void map() {
        this.columnMap = buildMap(this.resultClass);
    }

    private Map<String, Field> buildMap(Class<T> target) {
        return Arrays.stream(target.getDeclaredFields())
                .peek(f->f.setAccessible(true))
                .filter(f->f.isAnnotationPresent(Column.class))
                .collect(Collectors.toMap(
                    k->k.getAnnotation(Column.class).value(),
                    v->v));
    }

    @Override
    public Field get(String key) {
        return this.columnMap.get(key);
    }

    @Override
    public List<T> result(ResultSet resultSet) {
        this.resultSet = resultSet;
        return buildResult();
    }

    @Override
    public void mapper(T target, Field field, ResultSet rs, int i) {
        try {
            Class<?> type = field.getType();
            if (type.isAssignableFrom(String.class)) {
                field.set(target,rs.getString(i));
            } else if (type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)) {
                field.set(target, rs.getInt(i));
            } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
                field.set(target, rs.getBoolean(i));
            } else if (type.isAssignableFrom(Date.class)) {
                field.set(target, rs.getDate(i));
            } else if (type.isAssignableFrom(BigDecimal.class)) {
                field.set(target, rs.getBigDecimal(i));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private List<T> buildResult() {
        List<T> entities = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (this.resultSet.next()){
                T entity = newInstance(this.resultClass);
                for (int i= 1;i<=columnCount;i++){
                    String columnName = metaData.getColumnName(i);
                    Field field = this.columnMap.get(columnName);
                    if (field == null) continue;
                    mapper(entity,field,resultSet,i);
                }
                entities.add(entity);
            }

        } catch (Exception e) {
            throw new ResultMapperException("ResultSet Mapping Entity Error. "+e.getMessage());
        }
        return entities;
    }

    private T newInstance(Class<T> clazz) throws Exception{
        return (T) clazz.getDeclaredConstructor().newInstance();
    }


}
